/* @internal */
namespace ts.JsTyping {

    export interface TypingResolutionHost {
        directoryExists(path: string): boolean;
        fileExists(fileName: string): boolean;
        readFile(path: string, encoding?: string): string | undefined;
        readDirectory(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[] | undefined, depth?: number): string[];
    }

    export interface CachedTyping {
        typingLocation: string;
        version: Version;
    }

    export function isTypingUpToDate(cachedTyping: CachedTyping, availableTypingVersions: MapLike<string>) {
        const availableVersion = new Version(getProperty(availableTypingVersions, `ts${versionMajorMinor}`) || getProperty(availableTypingVersions, "latest")!);
        return availableVersion.compareTo(cachedTyping.version) <= 0;
    }

    export const nodeCoreModuleList: readonly string[] = [
        "assert",
        "async_hooks",
        "buffer",
        "child_process",
        "cluster",
        "console",
        "constants",
        "crypto",
        "dgram",
        "dns",
        "domain",
        "events",
        "fs",
        "http",
        "https",
        "http2",
        "inspector",
        "net",
        "os",
        "path",
        "perf_hooks",
        "process",
        "punycode",
        "querystring",
        "readline",
        "repl",
        "stream",
        "string_decoder",
        "timers",
        "tls",
        "tty",
        "url",
        "util",
        "v8",
        "vm",
        "zlib"
    ];

    export const nodeCoreModules = arrayToSet(nodeCoreModuleList);

    export function nonRelativeModuleNameForTypingCache(moduleName: string) {
        return nodeCoreModules.has(moduleName) ? "node" : moduleName;
    }

    /**
     * A map of loose file names to library names that we are confident require typings
     */
    export type SafeList = ReadonlyMap<string>;

    export function loadSafeList(host: TypingResolutionHost, safeListPath: Path): SafeList {
        const result = readConfigFile(safeListPath, path => host.readFile(path));
        return createMapFromTemplate<string>(result.config);
    }

    export function loadTypesMap(host: TypingResolutionHost, typesMapPath: Path): SafeList | undefined {
        const result = readConfigFile(typesMapPath, path => host.readFile(path));
        if (result.config) {
            return createMapFromTemplate<string>(result.config.simpleMap);
        }
        return undefined;
    }

    /**
     * @param host is the object providing I/O related operations.
     * @param fileNames are the file names that belong to the same project
     * @param projectRootPath is the path to the project root directory
     * @param safeListPath is the path used to retrieve the safe list
     * @param packageNameToTypingLocation is the map of package names to their cached typing locations and installed versions
     * @param typeAcquisition is used to customize the typing acquisition process
     * @param compilerOptions are used as a source for typing inference
     */
    export function discoverTypings():  { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } {
        return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] };
    }

    export const enum NameValidationResult {
        Ok,
        EmptyName,
        NameTooLong,
        NameStartsWithDot,
        NameStartsWithUnderscore,
        NameContainsNonURISafeCharacters
    }

    const maxPackageNameLength = 214;

    export interface ScopedPackageNameValidationResult {
        name: string;
        isScopeName: boolean;
        result: NameValidationResult;
    }
    export type PackageNameValidationResult = NameValidationResult | ScopedPackageNameValidationResult;

    /**
     * Validates package name using rules defined at https://docs.npmjs.com/files/package.json
     */
    export function validatePackageName(packageName: string): PackageNameValidationResult {
        return validatePackageNameWorker(packageName, /*supportScopedPackage*/ true);
    }

    function validatePackageNameWorker(packageName: string, supportScopedPackage: false): NameValidationResult;
    function validatePackageNameWorker(packageName: string, supportScopedPackage: true): PackageNameValidationResult;
    function validatePackageNameWorker(packageName: string, supportScopedPackage: boolean): PackageNameValidationResult {
        if (!packageName) {
            return NameValidationResult.EmptyName;
        }
        if (packageName.length > maxPackageNameLength) {
            return NameValidationResult.NameTooLong;
        }
        if (packageName.charCodeAt(0) === Charas.dot) {
            return NameValidationResult.NameStartsWithDot;
        }
        if (packageName.charCodeAt(0) === Charas._) {
            return NameValidationResult.NameStartsWithUnderscore;
        }
        // check if name is scope package like: starts with @ and has one '/' in the middle
        // scoped packages are not currently supported
        if (supportScopedPackage) {
            const matches = /^@([^/]+)\/([^/]+)$/.exec(packageName);
            if (matches) {
                const scopeResult = validatePackageNameWorker(matches[1], /*supportScopedPackage*/ false);
                if (scopeResult !== NameValidationResult.Ok) {
                    return { name: matches[1], isScopeName: true, result: scopeResult };
                }
                const packageResult = validatePackageNameWorker(matches[2], /*supportScopedPackage*/ false);
                if (packageResult !== NameValidationResult.Ok) {
                    return { name: matches[2], isScopeName: false, result: packageResult };
                }
                return NameValidationResult.Ok;
            }
        }
        if (encodeURIComponent(packageName) !== packageName) {
            return NameValidationResult.NameContainsNonURISafeCharacters;
        }
        return NameValidationResult.Ok;
    }

    export function renderPackageNameValidationFailure(result: PackageNameValidationResult, typing: string): string {
        return typeof result === "object" ?
            renderPackageNameValidationFailureWorker(typing, result.result, result.name, result.isScopeName) :
            renderPackageNameValidationFailureWorker(typing, result, typing, /*isScopeName*/ false);
    }

    function renderPackageNameValidationFailureWorker(typing: string, result: NameValidationResult, name: string, isScopeName: boolean): string {
        const kind = isScopeName ? "Scope" : "Package";
        switch (result) {
            case NameValidationResult.EmptyName:
                return `'${typing}':: ${kind} name '${name}' cannot be empty`;
            case NameValidationResult.NameTooLong:
                return `'${typing}':: ${kind} name '${name}' should be less than ${maxPackageNameLength} characters`;
            case NameValidationResult.NameStartsWithDot:
                return `'${typing}':: ${kind} name '${name}' cannot start with '.'`;
            case NameValidationResult.NameStartsWithUnderscore:
                return `'${typing}':: ${kind} name '${name}' cannot start with '_'`;
            case NameValidationResult.NameContainsNonURISafeCharacters:
                return `'${typing}':: ${kind} name '${name}' contains non URI safe characters`;
            case NameValidationResult.Ok:
                return Debug.fail(); // Shouldn't have called this.
            default:
                throw Debug.assertNever(result);
        }
    }
}
